using System;
using System.Net.Sockets;
using System.Threading;
using System.Collections;
using System.IO;
using System.Text;

namespace GeoFramework.IO
{
    /// <summary>
    /// Provides shared read access to a single underlying stream.
    /// </summary>
    /// <remarks>This class allows a Stream to be read from by multiple subscribers
    /// simultaneously.  This class is typically used for situations where the underlying
    /// stream is locked to one reader during use, such as a serial or Bluetooth connection.
    /// Calling the "GetStream" method will cause a new subscription to be made to the
    /// underlying stream.</remarks>
	public sealed class SharedReadStream : Stream
	{
        private Stream pBaseStream;
        private bool pIsStreamClosed;
        private ArrayList pSubscribers = new ArrayList();
        private DualChannelStream pDefaultStream;
        private Thread ReadingThread;
        private int pBufferSize = 45;
        private int pMaximumCapacity = 8192;
#if PocketPC
        private ThreadPriority pThreadPriority = ThreadPriority.AboveNormal;
#else
        private ThreadPriority pThreadPriority = ThreadPriority.Normal;
#endif

        /// <summary>
        /// Creates a new instance using the specified Stream as its underlying data source.
        /// </summary>
        /// <param name="baseStream"></param>
        public SharedReadStream(Stream baseStream)
        {
            // Record the base stream
            pBaseStream = baseStream;

            // Force the underlying stream to open
            long len = pBaseStream.Length;

            // Create a default subscriber
            pDefaultStream = GetStream();

            // Launch a thread to read incoming data
            ReadingThread = new Thread(new ThreadStart(ReadingThreadProc));
#if !PocketPC || Framework20
			ReadingThread.Name = "GeoFrameworks Shared Stream Reading Thread (http://www.geoframeworks.com)";
            ReadingThread.IsBackground = true;
#endif
#if PocketPC 
			ReadingThread.Priority = pThreadPriority;
#endif
            ReadingThread.Start();
        }

        /// <summary>
        /// Creates a new stream to receive data from the underlying stream.
        /// </summary>
        /// <returns>A new Stream object which will receive data from the underlying stream.</returns>
        /// <remarks>This method will create a new thread-safe MemoryStream, which will then 
        /// immediately start receiving data once it is available.  </remarks>
		public DualChannelStream GetStream()
		{
			// Add a new subscriber to the stream
            DualChannelStream NewStream = new DualChannelStream(new MemoryStream(pBufferSize), pBaseStream);
            lock (pSubscribers.SyncRoot)
            {
                pSubscribers.Add(NewStream);
            }
            return NewStream;
        }

        /// <summary>
        /// Controls the priority of the thread responsible for reading data from the underlying stream.
        /// </summary>
		public ThreadPriority ThreadPriority 
		{
			get
			{
				return pThreadPriority;
			}
			set
			{
				pThreadPriority = value;
			}
		}

        public override string ToString()
        {
            if(pBaseStream != null)
                return "Multiple Reader Stream on " + pBaseStream.ToString();
            else
                return "Multiple Reader Stream (No underlying data source)";
        }

        ~SharedReadStream()
        {
            Close();
        }

        #region Overrides

        /// <summary>
        /// Controls the number of bytes read at a time from the underlying stream.
        /// </summary>
        /// <remarks>This property is used to control the number of calls to the Read method of the underlying stream.
        /// Higher values will result in less CPU use (since less I/O calls are made from the underlying stream), but a
        /// buffer value that is too high may cause latency of time-sensitive GPS data.  By default, this property is
        /// set to 64 bytes, about the average size of a single NMEA sentence.</remarks>
        public int BufferSize
        {
            get
            {
                return pBufferSize;
            }
            set
            {
                if (pBufferSize < 1)
#if !PocketPC || Framework20
                    throw new ArgumentOutOfRangeException("value", "The SharedReadStream buffer size cannot be below a value of one.  A value of 1024 is recommended to take advantage of read-buffering and reduced CPU usage.");
#else
					throw new ArgumentOutOfRangeException("The SharedReadStream buffer size cannot be below a value of one.  A value of 1024 is recommended to take advantage of read-buffering and reduced CPU usage.");
#endif

                pBufferSize = value;
            }
        }

        public override bool CanRead
        {
            get 
            {
                return pBaseStream.CanRead;    
            }
        }

        public override bool CanSeek
        {
            get 
            { 
                return pBaseStream.CanSeek; 
            }
        }

        public override bool CanWrite
        {
            get 
            {
                return pBaseStream.CanWrite;
            }
        }

        public override void Flush()
        {
            pBaseStream.Flush();
        }

        public override long Length
        {
            get 
            {
                return pDefaultStream.Length;
            }
        }

        public override long Position
        {
            get
            {
                return pDefaultStream.Position;
            }
            set
            {
                pDefaultStream.Position = value;
            }
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            return pDefaultStream.Read(buffer, offset, count);
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return pDefaultStream.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            pBaseStream.SetLength(value);
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            pBaseStream.Write(buffer, offset, count);
        }

        public override void Close()
        {
            if (pIsStreamClosed)
                return;

            pIsStreamClosed = true;

            // Don't need to finalize
            GC.SuppressFinalize(this);

            // Close the underlying stream
            pBaseStream.Close();

			// Close each stream in the collection
			lock (pSubscribers.SyncRoot)
			{
				foreach (Stream subscriber in pSubscribers)
				{
					subscriber.Close();
				}
			}

#if Framework20
            // Dispose of all subscribers
            Dispose(true);
#endif

#if !PocketPC || Framework20
            // Shut down the thread
            if(ReadingThread != null && !ReadingThread.Join(500))
            {
                try
                {
                    ReadingThread.Abort();
                }
                catch
                {
                }
            }
#endif
        }

        public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
        {
            return pDefaultStream.BeginRead(buffer, offset, count, callback, state);
        }

        public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
        {
            return pBaseStream.BeginWrite(buffer, offset, count, callback, state);
        }

        public override int EndRead(IAsyncResult asyncResult)
        {
            return pDefaultStream.EndRead(asyncResult);
        }

        public override void EndWrite(IAsyncResult asyncResult)
        {
            pBaseStream.EndWrite(asyncResult);
        }

        public override int ReadByte()
        {
			return pDefaultStream.ReadByte();
        }

        public override void WriteByte(byte value)
        {
            pBaseStream.WriteByte(value);
        }

        #endregion

        public void ReadingThreadProc()
        {
            // Stores streams which have been closed 
            ArrayList DisposedStreams = new ArrayList();
            long OldPosition;

            // See if data is available
            while (!pIsStreamClosed)
            {
                try
                {
					// Change the reading priority if necessary
#if PocketPC || !Framework20
#if !Framework10
					if (ReadingThread.Priority != pThreadPriority && Environment.OSVersion.Platform == PlatformID.WinCE)
#endif
						ReadingThread.Priority = pThreadPriority;
#endif

                    // Get the number of bytes available
                    long BytesAvailable = pBaseStream.Length;

                    // Get the maximum: the max buffer size or the bytes available
                    long BytesToRead = BytesAvailable > pBufferSize ? BytesAvailable : pBufferSize;

                    // Create a receiving buffer of this size
                    byte[] IncomingBuffer = new byte[BytesToRead];

                    // Declare an int to store the actual bytes read
                    int BytesRead = -1;

                    // Attempt to read data
                    BytesRead = pBaseStream.Read(IncomingBuffer, 0, (int)BytesToRead);

                    // Was anything read?
                    if (BytesRead <= 0)
                    {
                        // No.  Wait a bit and try again
                        Thread.Sleep(100);
                        continue;
                    }

                    // Distribute the new data to all subscribing streams
                    lock (pSubscribers.SyncRoot)
                    {
                        // Deal out all new data to the waiting streams
                        foreach (DualChannelStream subscriber in pSubscribers)
                        {
                            try
                            {
                                // Flush the buffer if the position is at the end
                                lock (subscriber.ReadingSyncRoot)
                                {
                                    // If the stream is getting too big, and all data is read, flush the stream out
                                    if (subscriber.ReadingStream.Length > pMaximumCapacity && subscriber.ReadingStream.Position == subscriber.ReadingStream.Length)
                                    {
                                        // Erase all data in the memory stream
                                        subscriber.ReadingStream.SetLength(0);
                                        // And write the buffer
                                        subscriber.ReadingStream.Write(IncomingBuffer, 0, BytesRead);
                                        // Move to the beginning
                                        subscriber.ReadingStream.Seek(0, SeekOrigin.Begin);
                                    }
                                    else
                                    {
                                        // Remember the original reading position
                                        OldPosition = subscriber.ReadingStream.Position;
                                        // Move to the end of the stream
                                        subscriber.ReadingStream.Seek(0, SeekOrigin.End);
                                        // Append the new data
                                        subscriber.ReadingStream.Write(IncomingBuffer, 0, BytesRead);
                                        // And return to the original position
                                        subscriber.ReadingStream.Seek(OldPosition, SeekOrigin.Begin);
                                    }
                                }
                            }
                            catch (ObjectDisposedException)
                            {
                                // And record this stream as disposed
                                DisposedStreams.Add(subscriber);
                            }
                        }

                        // Was anything disposed?
                        if (DisposedStreams.Count == 0)
                            // Nope.  Keep reading
                            continue;

                        // Yes.  Remove all disposed streams to maintain stability
                        foreach (MemoryStream stream in DisposedStreams)
                        {
                            if (pSubscribers.Contains(stream))
                                pSubscribers.Remove(stream);
                        }

                        // Remove all disposed streams
                        DisposedStreams.Clear();
                    }
                }
#if !PocketPC || Framework20
                catch (ThreadAbortException)
                {
                    // Just exit!
                    return;
                }
#endif
                catch (ObjectDisposedException)
                {
                    // The underlying stream is closed.  Close us too!
                    Close();
                    return;
                }
                catch
                {
                    // Ignore?
                }
                finally
                {
                    //Thread.Sleep(100);
                    Thread.Sleep(0);
                }
            }
        }
    }

	/// <summary>
	/// Represents a single stream which performs read and write operations on two separate streams.
	/// </summary>
    /// <remarks>This class is typically used for situations where read operations must take place on
    /// a separate stream from write operations.  For example, the SharedReadStream class uses this
    /// class to provide multiple copies of incoming data from a GPS device while maintaining a single stream for
    /// write operations.  Since this class is fully thread-safe, the BluetoothStream class also utilizes
    /// this class for Bluetooth communications.</remarks>
	public sealed class DualChannelStream : Stream
	{
        private object _ReadSyncRoot = new object();
        private object _WriteSyncRoot = new object();
        private Stream _ReadStream;
        private Stream _WriteStream;

        public DualChannelStream(Stream readingStream, Stream writingStream)
		{
            _ReadStream = readingStream;
            _WriteStream = writingStream;
		}

		public override int Read(byte[] buffer, int offset, int count)
		{
			lock(_ReadSyncRoot)
			{
				return _ReadStream.Read(buffer, offset, count);
			}
		}

		public override int ReadByte()
		{
			lock(_ReadSyncRoot)
			{
				return _ReadStream.ReadByte();
			}
		}

		public override void Write(byte[] buffer, int offset, int count)
		{
			lock(_WriteSyncRoot)
			{
                _WriteStream.Write(buffer, offset, count);
			}
		}

		public override void WriteByte(byte value)
		{
            lock (_WriteSyncRoot)
            {
                _WriteStream.WriteByte(value);
            }
		}

		public object WritingSyncRoot
		{
			get
			{
                return _WriteSyncRoot;
			}
		}

        public Stream ReadingStream
        {
            get
            {
                return _ReadStream;
            }
        }

        public Stream WritingStream
        {
            get
            {
                return _WriteStream;
            }
        }

        public object ReadingSyncRoot
        {
            get
            {
                return _ReadSyncRoot;
            }
        }

        public override bool CanRead
        {
            get 
            { 
                return _ReadStream.CanRead; 
            }
        }

        public override bool CanSeek
        {
            get
            {
                return _ReadStream.CanSeek;
            }
        }

        public override bool CanWrite
        {
            get 
            {
                return _ReadStream.CanWrite;
            }
        }

        public override void Flush()
        {
            lock (_WriteSyncRoot)
            {
                _ReadStream.Flush();
            }
        }

        public override long Length
        {
            get 
            {
                lock (_ReadSyncRoot)
                {
                    NetworkStream Base = _ReadStream as NetworkStream;
                    if(Base != null)
                        return Base.DataAvailable ? 1 : 0;
                    else
                        return _ReadStream.Length;
                }
            }
        }

        public override long Position
        {
            get
            {
                lock (_ReadSyncRoot)
                {
                    return _ReadStream.Position;
                }
            }
            set
            {
                lock (_ReadSyncRoot)
                {
                    _ReadStream.Position = value;
                }
            }
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            lock (_ReadSyncRoot)
            {
                return _ReadStream.Seek(offset, origin);
            }
        }

        public override void SetLength(long value)
        {
            lock (_WriteSyncRoot)
            {
                _WriteStream.SetLength(value);
            }
        }
    }
}
